use std::path::Path;
use std::fs::File;
use std::io::Write;
use std::io::{BufRead, BufReader};
use super::sphere::Sphere;
use super::traits::Skynet;
use super::distance;

pub struct Boxes<'a, T> {
    pub n: usize, // 维度
    pub name: &'a str, // 名字
    pub begin: Vec<Vec<T>>,
    pub end: Vec<Vec<T>>
}

impl<'a, 'b, T: Skynet<T>> Boxes<'a, T> {
    pub fn new(n: usize, name: &'a str) -> Boxes<'a, T> {
        return Boxes {n: n, name: name, begin: Vec::new(), end: Vec::new()};
    }
    pub fn load(&mut self, path: &Path) {
        let file = File::open(path).unwrap();
        let buf = BufReader::new(file);
        for line in buf.lines() {
            let mut p: Vec<T> = Vec::new();
            for x in line.unwrap().split_whitespace() {
                p.push(x.parse().unwrap());
            }
            let mut a: Vec<T> = Vec::new();
            let mut b: Vec<T> = Vec::new();
            let n = p.len() / 2;
            assert_eq!(n + n, p.len());
            for i in 0 .. n {
                a.push(p[i]);
                b.push(p[i + n]);
            }
            self.begin.push(a);
            self.end.push(b);
        }
    }
    pub fn bounding_sphere(&self) -> Sphere<T> {
        let mut sphere: Sphere<T> = Sphere::new(self.n);
        // 计算包围球中心
        let n: T = T::from(2.0) * (self.begin.len() as f64).into();
        for x_i in &self.begin {
            for j in 0 .. self.n {
                sphere.center[j] += x_i[j] / n;
            }
        }
        for x_i in &self.end {
            for j in 0 .. self.n {
                sphere.center[j] += x_i[j] / n;
            }
        }        
        // 计算包围球半径
        for x in &self.begin {
            let d = distance(x, &sphere.center);
            if sphere.radius < d {
                sphere.radius = d;
            }
        }
        for x in &self.end {
            let d = distance(x, &sphere.center);
            if sphere.radius < d {
                sphere.radius = d;
            }
        }
        return sphere;
    }
    fn box_vertices(a: &Vec<T>, b: &Vec<T>) -> Vec<Vec<T>> {
        assert_eq!(a.len(), b.len());
        let mut vs: Vec<Vec<T>> = Vec::new();
        let dim = a.len();
        let mut k = 1;
        vs.push(a.clone());
        for i in 0 .. dim {
            for j in 0 .. k {
                let mut v = Vec::new();
                for h in 0 .. dim {
                    v.push(vs[j][h]);
                }
                v[i] += b[i] - a[i];
                vs.push(v);
            }
            k *= 2;
        }
        return vs;
    }
    fn create_paths(vs: &'b Vec<Vec<T>>, box_size: &Vec<T>) -> Vec<Vec<&'b Vec<T>>> {
        let mut paths: Vec<Vec<&Vec<T>>> = Vec::new();
        let n = vs.len();
        for i in 0 .. n {
            let start = &vs[i];
            for j in i .. n {
                let maybe_stop = &vs[j];
                let d = distance(start, maybe_stop);
                let mut hit = 0;
                for t in box_size {
                    if (d - *t).abs() < T::from(f64::EPSILON) {
                        hit = hit + 1;
                    }
                }
                if hit > 0 {
                    let mut path: Vec<&Vec<T>> = Vec::new();
                    path.push(start);
                    path.push(maybe_stop);
                    paths.push(path);
                }
            }
        }
        return paths;
    }
    fn paths_sort(paths: &mut Vec<Vec<&Vec<T>>>) {
        for path in paths {
            let a = path[0];
            let b = path[1];
            let mut c: Vec<T> = Vec::new();
            for i in 0 .. a.len() {
                c.push(b[i] - a[i]);
            }
            for d in c {
                if d < T::from(0.0) {
                    path[0] = b;
                    path[1] = a;
                    break;
                }
            }
        }
    }
    pub fn output_model(&self) {
        assert_eq!(self.n, 3);
        let mut boxes = String::new();
        boxes += format!("#declare {}_data = union {{\n", self.name).as_str();
        for i in 0 .. self.begin.len() {
            let a = &self.begin[i];
            let b = &self.end[i];
            boxes += format!("  box {{<{:.6}, {:.6}, {:.6}>, \
                              <{:.6}, {:.6}, {:.6}>}}\n",
                             a[0], a[1], a[2].inv(),
                             b[0], b[1], b[2].inv()).as_str();
        }
        boxes += "}\n";
        boxes += format!("#declare {}_frame_data = union {{\n", self.name).as_str();
        for i in 0 .. self.begin.len() {
            let a = &self.begin[i];
            let b = &self.end[i];
            let vs = Self::box_vertices(a, b);
            let mut box_size: Vec<T> = Vec::new();
            for j in 0 .. a.len() {
                box_size.push(b[j] - a[j]);
            }
            let mut paths = Self::create_paths(&vs, &box_size);
            Self::paths_sort(&mut paths);
            for path in paths {
                let c = path[0];
                let d = path[1];
                boxes += format!("  box {{<{:.6}, {:.6}, {:.6}> - 0.5 *line_width, \
                                  <{:.6}, {:.6}, {:.6}> + 0.5 * line_width}}\n",
                                 c[0], c[1], c[2].inv(), d[0], d[1], d[2].inv()).as_str();
            }
        }
        boxes += "}\n";
        let mut boxes_file =
            File::create(Path::new(format!("{}.inc", self.name).as_str())).unwrap();
        boxes_file.write_all(boxes.as_bytes()).unwrap();

         // 输出 Povray 场景对象
        let mut object = String::new();
        object += format!("#include \"{}.inc\"\n", self.name).as_str();
        object += "object {\n";
        object += format!("  {}_data\n", self.name).as_str();
        object += "  texture {pigment {color rgb <0.3, 1.0, 0.04, 0.75>}}\n";
        object += "}\n";
        object += "object {\n";
        object += format!("  {}_frame_data\n", self.name).as_str();
        object += "  texture {pigment {color rgb <0.6, 0.6, 0.6>}}\n";
        object += "}\n";
        let object_path = format!("{}_object.inc", self.name);
        let mut object_file = File::create(Path::new(object_path.as_str())).unwrap();
        object_file.write_all(object.as_bytes()).unwrap();
    }
}
